#include <string.h>
#include <unistd.h>

#include "../../common/error_code.h"
#include "../../common/log.h"
#include "../fd_event_group.h"
#include "tcp_connection.h"

#include "tcp_client.h"

namespace rocket {

TcpClient::TcpClient(NetAddr::s_ptr peer_addr) : m_peer_addr(peer_addr) {
    m_event_loop = EventLoop::GetCurrentEventLoop();
    m_fd = socket(peer_addr->getFamily(), SOCK_STREAM, 0);

    if (m_fd < 0) {
        ERRORLOG("TcpClient::TcpClient() error, failed to create fd");
        return;
    }

    m_fd_event = FdEventGroup::GetFdEventGroup()->getFdEvent(m_fd);
    m_fd_event->setNonBlock();

    m_connection = std::make_shared<TcpConnection>(
        m_event_loop, m_fd, 128, nullptr, peer_addr,
        TcpConnection::TcpConnectionByClient);
}

TcpClient::~TcpClient() {
    DEBUGLOG("TcpCLient::~TcpClient()");
    if (m_fd > 0) {
        close(m_fd);
    }
}

void TcpClient::connect(std::function<void()> done) {
    int rt =
        ::connect(m_fd, m_peer_addr->getSockAddr(), m_peer_addr->getSockLen());
    if (rt == 0) {
        DEBUGLOG("connect [%s] success.", m_peer_addr->toString().c_str());
        initLocalAddr();
        m_connection->setState(TcpConnection::Connected);
        if (done) {
            done();
        }
    } else if (rt == -1) {
        if (errno == EINPROGRESS) {
            // 监听可写事件，判断 errcode
            auto writeDone = [this, done]() {
                int result = ::connect(m_fd, m_peer_addr->getSockAddr(), m_peer_addr->getSockLen());
                if ((result < 0 && errno == EISCONN) || (result == 0)) {
                    DEBUGLOG("connect [%s] success.",
                             m_peer_addr->toString().c_str());
                    initLocalAddr();
                    m_connection->setState(TcpConnection::Connected);
                } else {
                    if (errno == ECONNREFUSED) {
                        m_connect_error_code = ERROR_PEER_CLOSED;
                        m_connect_error_info = "connection refused, sys error = " + std::string(strerror(errno));
                    } else {
                        m_connect_error_code = ERROR_FAILED_CONNECT;
                        m_connect_error_info = "connection unkown error, sys error = " + std::string(strerror(errno));
                    }
                    ERRORLOG("connect error, errno=%d, error=%s", errno,
                             strerror(errno));
                    close(m_fd);
                    m_fd = socket(m_peer_addr->getFamily(), SOCK_STREAM, 0);
                }

                m_event_loop->deleteEpollEvent(m_fd_event);
                if (done) {
                    done();
                }
            };
            m_fd_event->listen(FdEvent::OUT_EVENT, writeDone);
            m_event_loop->addEpollEvent(m_fd_event);

            if (!m_event_loop->isLooping()) {
                m_event_loop->loop();
            }

        } else {
            ERRORLOG("connect error, errno=%d, error=%s", errno,
                     strerror(errno));
            m_connect_error_code = ERROR_FAILED_CONNECT;
            m_connect_error_info = "connect error, sys error = " + std::string(strerror(errno));
            if (done) {
                done();
            }
        }
    }
}

void TcpClient::writeMessage(
    AbstractProtocol::s_ptr message,
    std::function<void(AbstractProtocol::s_ptr)> done) {
    // 1. 写 message 和 done 到 Connection 的 Buffer 中
    // 2. 启动 connection 写事件监听
    m_connection->pushSendMessage(message, done);
    m_connection->listenWrite();
}

void TcpClient::readMessage(const std::string &req_id,
                            std::function<void(AbstractProtocol::s_ptr)> done) {
    // 1. 监听可读事件
    // 2. 从 buffer 中 decode 得到 message,判断 req id 是否相等，执行回调。
    m_connection->pushReadMessage(req_id, done);
    m_connection->listenRead();
}

void TcpClient::stop() {
    if (m_event_loop->isLooping()) {
        m_event_loop->stop();
    }
}

int TcpClient::getConnectErrorCode() {
    return m_connect_error_code;
}

std::string TcpClient::getConnectErrorInfo() {
    return m_connect_error_info;
}

NetAddr::s_ptr TcpClient::getPeerAddr() {
    return m_peer_addr;
}

NetAddr::s_ptr TcpClient::getLocalAddr() {
    return m_local_addr;
}

void TcpClient::initLocalAddr() {
    if (m_local_addr != nullptr) {
        return ;
    }
    sockaddr_in local_addr;
    socklen_t len = sizeof(local_addr);
    int result = getsockname(m_fd, reinterpret_cast<sockaddr*>(&local_addr), &len);
    if (result != 0) {
        ERRORLOG("initLocalAddr error, getsockname error. errno=%d, error=%s", errno, strerror(errno));
        return ;
    }

    m_local_addr = std::make_shared<IPNetAddr>(local_addr);
}

void TcpClient::addTimerEvent(TimerEvent::s_ptr timer_event) {
    m_event_loop->addTimeEvent(timer_event);
}

} // namespace rocket
